package com.gzz.orchid.common.boot.mybatis.interceptor;


import com.gzz.orchid.common.boot.mybatis.utils.MybatisUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.executor.resultset.ResultSetHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.mapping.SqlSource;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;

import java.lang.reflect.Method;
import java.sql.PreparedStatement;
import java.time.Duration;
import java.util.Properties;

/**
 * @author higmd
 */
@Slf4j
@Intercepts({
        @Signature(method = "query", type = Executor.class, args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
        @Signature(method = "query", type = Executor.class, args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}),
        @Signature(method = "update", type = Executor.class, args = {MappedStatement.class, Object.class}),
        @Signature(method = "setParameters", type = ParameterHandler.class, args = {PreparedStatement.class}),
        @Signature(method = "getParameterObject", type = ParameterHandler.class, args = {}),
})
public class LogFormatterInterceptor implements Interceptor {
    private final Duration SLOWNESS_THRESHOLD = Duration.ofMillis(1000);

    /**
     * 代理对象每次调用的方法，就是要进行拦截的时候要执行的方法。在这个方法里面做我们自定义的逻辑处理;
     * 执行拦截内容的地方，比如想收点保护费。由plugin()⽅法触发，interceptor.plugin(target) 以证明。
     */
    @Override
    public Object intercept(Invocation inv) throws Throwable {
        log.info("进入日志格式化 Interceptor:  ..................");
        // 代理对象
        //Object target = inv.getTarget();
        // 代理方法
        //Method method = inv.getMethod();
        // 方法参数
        //Object[] args = inv.getArgs();

        MappedStatement ms = (MappedStatement) inv.getArgs()[0];
        String mapperId = ms.getId();
        SqlSource sqlSource = ms.getSqlSource();
        SqlCommandType commandType = ms.getSqlCommandType();
        log.info("==>命令类型 SqlCommandType: {}", commandType);
        log.info("==>SqlSource: {}", sqlSource);
        // 获取原始 SQL
         Object  paramObject= inv.getArgs()[1];
         BoundSql boundSql = ms.getBoundSql(paramObject);
        // log.info("==>Original SQL:"+ boundSql.getSql());

        // 获取执行 SQL 的耗时
        long start = System.currentTimeMillis();
        if(SqlCommandType.UPDATE.equals(commandType)){
            log.info("更新地址 ==> UPDATE SQL:");
            //Object object = ((MapperMethod.ParamMap) inv.getArgs()[1]).get("et");
            Object parameter = inv.getArgs()[1];
            MybatisUtils.handleUpdateListener(parameter);
        }else if(SqlCommandType.INSERT.equals(commandType)){
            log.info("插入地址 ==> INSERT SQL:");
            MybatisUtils.handleInsertListener(inv.getArgs()[1]);
        }else if(SqlCommandType.DELETE.equals(commandType)){
            log.info("删除地址 ==> DELETE SQL:");
            Object parameter = inv.getArgs()[1];
            log.info("参数值{}",parameter);
        }else {
            log.info("查询地址 ==> SELECT SQL:");
        }

        // 执行 SQL
        Object proceed = inv.proceed();
        long duration = System.currentTimeMillis() - start;
        // String formatterSql = MybatisUtils.extractSql(ms,inv);
        String finalSql = MybatisUtils.resolveSql(ms.getConfiguration(), boundSql);
        // 当 SQL 执行超过我们设置的阈值，转为 WARN 级别
        if (Duration.ofMillis(duration).compareTo(SLOWNESS_THRESHOLD) < 0) {
            log.info("\r\n方法名称:  {} \r\n执行SQL:  {} \r\n 执行耗时:  {} ms ", mapperId, finalSql, duration);
        } else {
            log.warn("\r\n方法名称: {} \r\n exec sql took more than {} ms: \r\n执行SQL: {} \r\n执行耗时:  {} ms"
                    , mapperId
                    , SLOWNESS_THRESHOLD.toMillis()
                    , finalSql
                    , duration);
        }
        // 打印 SQL方法二
        //Configuration configuration = mappedStatement.getConfiguration();
        //MybatisUtils.printSqlLog(configuration, boundSql);
        return proceed;
    }

    /**
     * 生成MyBatis拦截代理对象（非必须）
     *
     * 决定是否触发intercept()⽅法。
     * plugin方法是拦截器用于封装目标对象的，通过该方法我们可以返回目标对象本身，也可以返回一个它的代理
     * 当返回的是代理的时候我们可以对其中的方法进行拦截来调用intercept方法 -- Plugin.wrap(target, this)
     * 当返回的是当前对象的时候 就不会调用intercept方法，相当于当前拦截器无效
     * Plugin类是插件的核心类，给target创建一个JDK的动态代理对象，触发intercept()⽅法
     */
    @Override
    public Object plugin(Object target) {
       log.info("日志格式化Plugin: {}", target.getClass().getName());
        if (target instanceof Executor) {
            // 调用插件
            return Plugin.wrap(target, this);
        }else if (target instanceof ParameterHandler) {
            // 调用插件
            return Plugin.wrap(target, this);
        }
        return target;
    }

    /**
     * 给自定义的拦截器传递xml配置的属性参数。
     * 用于在Mybatis配置文件中指定一些属性的，注册当前拦截器的时候可以设置一些属性
     */
    @Override
    public void setProperties(Properties properties) {

        log.info("启用SQL日志格式化 Properties: {}", properties);
    }
}
